package ws.jdbc.concurrent.driver.sqlserver;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.concurrent.locks.ReentrantLock;
import ws.jdbc.concurrent.driver.sqlserver.tds.TDSMessage;

/**
 * Abstract implementation of {@link Statement} interface.
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public abstract class CStatement implements ICStatement {

  /** Connection object that created this statement. */
  protected CConnection connection;
  /** Result set type (forward-only versus scrollable). */
  protected final int resultSetType;
  /** Result set concurrency (read-only versus updatable). */
  protected final int resultSetConcurrency;
  /** Object used to communicate with the SQL Server. */
  protected TDSMessage message;
  /** Lock used by the created cursors. */
  protected final ReentrantLock lock;
  /** Result set cursor with individual cache (singleton). */
  protected CResultSet individualCursor;
  /** Number of rows that should be fetched from the database when more rows are needed. */
  protected int fetchSize = 20;
  /** Direction in which the created result set will be processed. */
  protected int fetchDirection = ResultSet.FETCH_FORWARD;
  /** Object created when {@code executeQuery} is executed. */
  protected CResultSet resultSet;
  /** Cache is shared amongst cursors. */
  protected boolean shared;
  /** SQL batch. */
  private LinkedList<String> batch;
  /** Statement id. Is used upon close to ask the connection to remove this statement from the list of created statements. */
  private final int id;

  /**
   * Create new statement instance.
   *
   * @param connection Connection object that created this statement.
   * @param message TDS message object with the connection established with the server.
   * @param resultSetType Result set type.sim
   * @param resultSetConcurrency Result set concurrency.
   */
  protected CStatement(final int id, final CConnection connection, final TDSMessage message, final int resultSetType, final int resultSetConcurrency) {
    this.id = id;
    this.connection = connection;
    this.message = message;

    this.resultSetType = resultSetType;
    this.resultSetConcurrency = resultSetConcurrency;

    this.shared = false;

    this.lock = new ReentrantLock();
  }

  public synchronized void close() throws SQLException {
    if (isClosed()) {
      return;
    }
    connection.removeStatement(id);
    closeWithoutRemove();
  }

  public synchronized int executeUpdate(String sql) throws SQLException {
    throwExceptionIfClosed();
    return message.executeSQLBatch(sql).getUpdateRowCount();
  }

  protected final void closeResultSet() throws SQLException {
    if (resultSet != null) {
      resultSet.close();
      resultSet = null;
    }
  }

  public synchronized void setShared(boolean shared) {
    this.shared = shared;
  }

  /**
   * Close this statement without trying to remove it from the list of statements of the parent connection.
   * 
   * @throws SQLException
   */
  final synchronized void closeWithoutRemove() throws SQLException {
    if (isClosed()) {
      return;
    }
    closeResultSet();
    connection = null;
    message = null;
  }

  public synchronized boolean isClosed() throws SQLException {
    return connection == null;
  }

  public TDSMessage getMessage() {
    return message;
  }

  public Connection getConnection() throws SQLException {
    return connection;
  }

  public void setFetchDirection(int direction) throws SQLException {
    this.fetchDirection = direction;
  }

  public int getFetchDirection() throws SQLException {
    return fetchDirection;
  }

  public void setFetchSize(int rows) throws SQLException {
    if (rows < 0) {
      throw new SQLException("'rows' must be greater or equal than zero (>= 0).");
    }
    if (rows == 0) {
      shared = true;
      fetchSize = Integer.MAX_VALUE;
    } else {
      shared = false;
      fetchSize = rows;
    }
  }

  public int getFetchSize() throws SQLException {
    return fetchSize;
  }

  public int getResultSetConcurrency() throws SQLException {
    return resultSetConcurrency;
  }

  public int getResultSetType() throws SQLException {
    return resultSetType;
  }

  public final synchronized void addBatch(String sql) throws SQLException {
    if (batch == null) {
      batch = new LinkedList<String>();
    }
    batch.add(sql);
  }

  public final synchronized void clearBatch() throws SQLException {
    if (batch != null) {
      batch.clear();
      batch = null;
    }
  }

  public final synchronized int[] executeBatch() throws SQLException {
    if (batch == null) {
      return null;
    }
    int[] ret = new int[batch.size()];
    for (int i = 0; i < batch.size(); ++i) {
      /** Another thread might be using the 'message' object so it's better to lock. */
      lock.lock();
      try {
        ret[i] = message.executeSQLBatch(batch.get(i)).getUpdateRowCount();
        // TODO: check for errors (BatchUpdateException)
      } finally {
        lock.unlock();
      }
    }
    return ret;
  }

  protected final void throwExceptionIfClosed() throws SQLException {
    if (isClosed()) {
      throw new SQLException(Messages.msg_statement_is_closed);
    }
  }

  protected final void throwExceptionIfResultSetIsClosed() throws SQLException {
    if (resultSet == null || resultSet.isClosed()) {
      throw new SQLException(Messages.msg_resultset_is_closed);
    }
  }

  // <editor-fold defaultstate="collapsed" desc="Not implemented.">
  public ResultSet executeQuery(String sql) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int getMaxFieldSize() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setMaxFieldSize(int max) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int getMaxRows() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setMaxRows(int max) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setEscapeProcessing(boolean enable) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int getQueryTimeout() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setQueryTimeout(int seconds) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void cancel() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public SQLWarning getWarnings() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void clearWarnings() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setCursorName(String name) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean execute(String sql) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public ResultSet getResultSet() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int getUpdateCount() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean getMoreResults() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean getMoreResults(int current) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public ResultSet getGeneratedKeys() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int executeUpdate(String sql, String[] columnNames) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean execute(String sql, int[] columnIndexes) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean execute(String sql, String[] columnNames) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int getResultSetHoldability() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setPoolable(boolean poolable) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean isPoolable() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public <T> T unwrap(Class<T> iface) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }
  // </editor-fold>
}
